package top.codedance.iotp.server;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.Version;
import top.codedance.iotp.common.entity.DeviceEntity;
import top.codedance.iotp.common.entity.URLEntity;
import top.codedance.iotp.common.entity.UserEntity;
import top.codedance.iotp.common.util.*;
import top.codedance.iotp.common.util.db.DaoUtil;
import top.codedance.iotp.common.util.db.UserDaoUtil;
import top.codedance.iotp.common.util.db.DeviceDaoUtil;
import top.codedance.iotp.loadblance.algorithm.Adapter;
import top.codedance.iotp.loadblance.algorithm.Func;
import top.codedance.iotp.loadblance.common.AlgorithmType;
import top.codedance.iotp.loadblance.entiy.RegistyUri;
import top.codedance.iotp.server.net.*;
import top.codedance.iotp.server.net.handle.configuration.CliCmdOptionsConfig;
import top.codedance.iotp.server.net.handle.util.DeviceService;
import top.codedance.iotp.server.net.handle.util.UserService;
import top.codedance.iotp.server.net.tools.MailUtil;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ServerApplication {
    public static final Long version = Long.valueOf(Version.VERSION); //server 版本
    public static final String TMP_FILE_NAME = "iot-server-init";
    public static String clientUpgradeReposity = "https://res.codedance.top"; //客户端升级库
    public static volatile long lastClientVersionCode = 2; //当前最新的客户端版本号
    public static boolean compatible = true; //是否兼容高版本的客户端
    public static boolean userThridUserService = false; //是否使用第三方用户
    public static boolean enableIpBindPort = false; //如果启用，那么服务不能有代理，网卡Ip即为外部访问ip才可使用
    public static String bindIp = "0.0.0.0"; //绑定提供服务的ip地址
    public static String httpBindHost = "127.0.0.1"; //绑定HTTP提供服务的域名
    public static String wsBindHost = "127.0.0.1"; //绑定Websocket提供服务的域名
    public static int tcpPort;
    public static int httpPort;
    public static int wsPort;
    public static CommandCli commandCli;
    public static ExecutorService pool; //线程池管理
    public static volatile long dataVersion = 0; //数据续列
    public static DaoUtil deviceDaoUtil;
    public static DaoUtil userDaoUtil;
    public static MailUtil mailUtil;
    public static VersionFileUtil versionFileUtil;
    public static ExecutorService workerPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); //工作任务线程池
    public static Boolean enableDistributed = false; //是否启用分布式
    public static String[] registryHosts = new String[]{"http://127.0.0.1:10014"}; //注册中心hosts
    public static String netUsername = ""; //此为与注册中心通讯用的用户名，是在启动server时指定的，不是注册中心给的用户名
    public static String netPassword = ""; //此为与注册中心通讯用的密码，是在启动server时指定的，不是注册中心给的密码
    public static Boolean enabalMail = false;
    public static Boolean enableCache = true;
    public static volatile List<UserEntity> users; //用户缓存
    public static volatile List<DeviceEntity> devicies; //设备缓存
    public static AtomicInteger tcpConnectCounter = new AtomicInteger(0); //tcp连接数
    public static AtomicInteger httpConnectCounter = new AtomicInteger(0); //http连接数
    public static AtomicInteger wsConnectCounter = new AtomicInteger(0); //ws连接数
    public static boolean isSafyMode = false; //是否是安全模式
    public static AtomicInteger consoleCounter = new AtomicInteger(0); //管理终端计数器连接数
    public static List<String> blacklist = new ArrayList<>(); //tcp客户端黑名单
    public static Timer timer = new Timer();
    public static RegistryExecutor registryExecutor;
    public static AlgorithmType at = AlgorithmType.AUTO;
    public static int weight = 0;
    public static volatile HashMap<String, String> clientsLoadInfo = new HashMap<>(); //key: deviceId, value: JSONObject.toString
    public static volatile HashMap<String, String> clientsOsInfo = new HashMap<>(); //key: deviceId, value: String
    private static boolean useAdvancedHttpServer = false;
    public static int ioThreads = Runtime.getRuntime().availableProcessors() * 2;
    public static int workerThreads = ioThreads * 4;
    public static String userVerifitionPrivateKey = null;
    public static boolean verifyEncryption = false;

    private static Logger logger;

    public static void main(String[] args) {
        System.setProperty("java.net.preferIPv4Stack", "true");
        System.setProperty("io.netty.noPreferDirect", "true");
        System.setProperty("io.netty.tmpdir", OSinfo.isWindows() ? System.getProperty("java.io.tmpdir") : "/var/tmp");
        versionFileUtil = new VersionFileUtil(ServerApplication.class); //版本操作工具
        commandCli = new CommandCli(args, CliCmdOptionsConfig.getCmds());
        enableDistributed = commandCli.getArg("enableDistributed", false);
        pool = Executors.newFixedThreadPool(enableDistributed ? 4 : 3); //线程池管理
        logger = ServerApplication.getLogger(ServerApplication.class);
        logger.debug("------------------------current work dir: " + SystemUtil.getRuntimeDirPath(ServerApplication.class) + "------------------------");
        Properties properties = new Properties();
        try {
            properties.load(ServerApplication.class.getResourceAsStream("/db.properties"));
        } catch (IOException e) {
            logger.debug(e.getMessage());
        }
        verifyEncryption = commandCli.getArg("verifyEncryption", false);
        if(verifyEncryption) {
            userVerifitionPrivateKey = readVerificationPrivateKey();
        }
        deviceDaoUtil = new DeviceDaoUtil(ServerApplication.class, getLogger(DeviceDaoUtil.class), properties); //设备管理Dao
        userDaoUtil = new UserDaoUtil(ServerApplication.class, getLogger(UserDaoUtil.class), properties); //用户管理Dao
        Properties props = new Properties();
        try {
            props.load(ServerApplication.class.getResourceAsStream("/mail.properties"));
        } catch (IOException e) {
            logger.debug(e.getMessage());
        }
        mailUtil = new MailUtil(props);
        enableIpBindPort = commandCli.getArg("enableIpBindPort", false);
        bindIp = commandCli.getArg("bindIp", "0.0.0.0");
        String defaultRequestHost = bindIp;
        if("0.0.0.0".equals(defaultRequestHost)){
            defaultRequestHost = "127.0.0.1";
        }
        httpPort = commandCli.getArg("httpPort", 10012);
        wsPort = commandCli.getArg("webSocketPort", 10013);
        httpBindHost = commandCli.getArg("httpBindHost",  "http://" + defaultRequestHost + ":" + httpPort);
        URLEntity httpBindHostUrlEntity = URLUtils.parseURL(httpBindHost);
        if(httpBindHostUrlEntity.getPort() != httpPort){
            httpBindHostUrlEntity.setPort(httpPort);
            httpBindHost = URLUtils.genURL(httpBindHostUrlEntity);
        }
        wsBindHost = commandCli.getArg("wsBindHost", "ws://" + defaultRequestHost + ":" + wsPort);
        URLEntity wsBindHostUrlEntity = URLUtils.parseURL(wsBindHost);
        if(wsBindHostUrlEntity.getPort() != wsPort){
            wsBindHostUrlEntity.setPort(wsPort);
            wsBindHost = URLUtils.genURL(wsBindHostUrlEntity);
        }
        userThridUserService = commandCli.getArg("useThridServer", false);
        registryHosts = commandCli.getArg("registryHosts",  registryHosts[0]).split("\\|");
        netUsername = commandCli.getArg("netUsername", UUID.randomUUID().toString());
        netPassword = commandCli.getArg("netPassword", UUID.randomUUID().toString());
        enabalMail = commandCli.getArg("enabalMail", false);
        enableCache = commandCli.getArg("enabalCache", false);
        weight = commandCli.getArg("weight", 0);
        compatible = commandCli.getArg("compatible", false);
        useAdvancedHttpServer = commandCli.getArg("useAdvancedHttpServer", false);
        ioThreads = commandCli.getArg("ioThreads", Runtime.getRuntime().availableProcessors() * 2);
        workerThreads = commandCli.getArg("workerThreads", ioThreads * 4);

        //初始化用户缓存
        initUsersCache();
        //初始化设备缓存
        initDeviciesCache();

        run();
        deviceDaoUtil.destory();
        userDaoUtil.destory();
    }

    private static void run() {
        tcpPort = commandCli.getArg("tcpPort", 10011);
        pool.submit(() -> {
            while (true) {
                try {
                    new TcpServer(bindIp, tcpPort).run();
                } catch (Exception ex) {
                    ServerApplication.syslogInfo("Start TcpServer fail!");
                }
                try {
                    Thread.sleep(5000);
                    ServerApplication.syslogInfo("Restart TcpServer ... ");
                } catch (Exception ex) {
                    logger.debug(ex.getMessage());
                }
            }
        });

        pool.submit(() -> {
            while (true) {
                try {
                    if(useAdvancedHttpServer){
                        new AdvancedHttpServer(bindIp, httpPort).run();
                    }else {
                        new HttpServer(bindIp, httpPort).run();
                    }
                } catch (Exception ex) {
                    logger.debug(ex.getMessage());
                    ServerApplication.syslogInfo("Start HttpServer fail!");
                }
                try {
                    Thread.sleep(5000);
                    ServerApplication.syslogInfo("Restart HttpServer ... ");
                } catch (Exception ex) {
                    logger.debug(ex.getMessage());
                }
            }
        });

        pool.submit(() -> {
            while (true) {
                try {
                    new WsServer(bindIp, wsPort).run();
                } catch (Exception ex) {
                    logger.debug(ex.getMessage());
                    ServerApplication.syslogInfo("Start WebsocketServer fail!");
                }
                try {
                    Thread.sleep(5000);
                    ServerApplication.syslogInfo("Restart WebsocketServer ... ");
                } catch (Exception ex) {
                    logger.debug(ex.getMessage());
                }
            }
        });

        if (enableDistributed) {
            pool.submit(() -> {
                registryExecutor = new RegistryExecutor(bindIp, tcpPort, httpPort, wsPort);
                registryExecutor.run();
            });
        }

        try {
            pool.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            logger.info("thread pool finished, and restart task.");
        } finally {
            if (pool.isShutdown()) {
                pool = Executors.newFixedThreadPool(3);
                try {
                    Thread.sleep(2000);
                    ServerApplication.syslogInfo("Restarting ... ");
                } catch (Exception ex) {
                }
                run();
            }
        }
    }

    public static long getLastClientVersionCode() {
        return lastClientVersionCode;
    }

    public static void setLastClientVersionCode(long version){
        lastClientVersionCode = version;
    }

    /**
     * 获取当前数据版本
     *
     * @return
     */
    public static long getDataVersion() {
        if (dataVersion >= Long.MAX_VALUE) {
            dataVersion = 0;
        }
        dataVersion++;
        return dataVersion;
    }

    /**
     * 获取log对象并动态设置日志级别
     *
     * @param clazz
     * @return
     */
    public static Logger getLogger(Class<?> clazz) {
        Logger logger = Logger.getLogger(clazz);
        logger.setLevel(Level.toLevel(ServerApplication.commandCli.getArg("logLevel", "ERROR")));
        return logger;
    }

    /**
     * 系统日志输出，任何情况下都打印
     *
     * @param info
     */
    public static void syslogInfo(String info) {
        System.out.println(info);
    }

    /**
     * 初始化用户缓存
     */
    public static void initUsersCache() {
        if(userVerifitionPrivateKey != null) {
            ServerApplication.users = UserService.list(userVerifitionPrivateKey);
        }else{
            ServerApplication.users = UserService.list();
        }
    }

    /**
     * 通过缓存验证用户
     *
     * @param deviceId
     * @param username
     * @param password
     * @return
     */
    public static boolean userCacheVerification(String deviceId, String username, String password) {
        boolean isVerlid = false;
        for (UserEntity entity : users) {
            if (deviceId.equals(entity.getDeviceId()) && username.equals(entity.getUsername()) && password.equals(entity.getPassword())) {
                isVerlid = true;
                break;
            }
        }
        return isVerlid;
    }

    /**
     * 通过缓存验证管理员
     *
     * @param username
     * @param password
     * @return
     */
    public static boolean adminCacheVerification(String username, String password) {
        boolean isVerlid = false;
        for (UserEntity entity : users) {
            if (username.equals(entity.getUsername()) && password.equals(entity.getPassword()) && entity.isAdmin()) {
                isVerlid = true;
                break;
            }
        }
        return isVerlid;
    }

    /**
     * 初始化设备缓存
     */
    public static void initDeviciesCache() {
        if(userVerifitionPrivateKey != null) {
            ServerApplication.devicies = DeviceService.list(userVerifitionPrivateKey);
        }else{
            ServerApplication.devicies = DeviceService.list();
        }
    }

    //验证设备是否存在
    public static boolean isExits(DeviceEntity de) {
        if (enableCache) {
            for (DeviceEntity entity : devicies) {
                if (entity.getDeviceId().equals(de.getDeviceId())) {
                    return true;
                }
            }
            return false;
        } else {
            if(userVerifitionPrivateKey != null) {
                return DeviceService.isExits(de, userVerifitionPrivateKey);
            }else{
                return DeviceService.isExits(de);
            }
        }
    }

    /**
     * 获取服务信息
     *
     * @return
     */
    public static JSONObject getServerInfomation() {
        JSONObject data = new JSONObject();
        data.put("dataVersion", ServerApplication.dataVersion);
        data.put("sessionCount", TcpServer.sessionManager.getSessionCount());
        int clientCount = 0;
        for (String di : TcpServer.sessionManager.getClientDeviceIds()){
            if(!di.matches("^Console\\[\\d+\\]::.*") && !di.matches("^Manage::.*")){
                clientCount++;
            }
        }
        data.put("clientCount", clientCount);
        data.put("registedUserCount", ServerApplication.users.size());
        data.put("registedDeviceCount", ServerApplication.devicies.size());
        data.put("lastClientVersionCode", ServerApplication.getLastClientVersionCode());
        data.put("clientUpgradeReposity", ServerApplication.clientUpgradeReposity);
        data.put("registryHosts", ServerApplication.registryHosts);
        data.put("enableDistributed", ServerApplication.enableDistributed);
        data.put("enabalMail", ServerApplication.enabalMail);
        data.put("enableCache", ServerApplication.enableCache);
        data.put("userThridUser", ServerApplication.userThridUserService);
        data.put("applicationThreadCount", SystemUtil.getCurrentProcessThreadCount());
        JSONArray ja = new JSONArray();
        Set<Map.Entry<String, String>> entries = ServerApplication.clientsLoadInfo.entrySet();
        for (Map.Entry<String, String> item : entries){
            if(!TcpServer.sessionManager.isOnline(item.getKey())){
                ServerApplication.clientsLoadInfo.remove(item.getKey());
                ServerApplication.clientsOsInfo.remove(item.getKey());
                continue;
            }
            JSONObject tmpJo = new JSONObject();
            tmpJo.put("deviceId", item.getKey());
            tmpJo.put("load", JSONObject.parse(item.getValue()));
            tmpJo.put("OS", ServerApplication.clientsOsInfo.get(item.getKey()));
            ja.add(tmpJo);
        }
        data.put("clientsLoad", ja);
        return data;
    }

    /**
     * 获取客户端信息
     * @param deviceId
     * @return
     */
    public static JSONObject getClientInfomation(String deviceId) {
        JSONObject jo = new JSONObject();
        jo.put("load", ServerApplication.clientsLoadInfo.get(deviceId));
        jo.put("OS", ServerApplication.clientsOsInfo.get(deviceId));
        return jo;
    }

    /**
     * 通过负载均衡调用注册中心
     * @param func
     * @return
     */
    public static String loadBlanceRegists(Func<String> func){
        try {
            Adapter adapter = at.getAlgorithm().newInstance();
            List<RegistyUri> registyUris = new ArrayList<>();
            int i = 0;
            for(String url : registryHosts){
                registyUris.add(new RegistyUri(url, i));
                i++;
            }
            return adapter.execute(registyUris, func);
        }catch (Exception ex){
            logger.debug(ex.getMessage());
            return null;
        }
    }

    private static String readVerificationPrivateKey(){
        String privateKey = SystemUtil.readFile(SystemUtil.getRuntimeDirPath(ServerApplication.class) + "privateKey.txt").trim();
        if(privateKey.isEmpty()){
            privateKey = null;
        }
        return privateKey;
    }
}
